package com.ebupt.migu.music.autotri.service.impl;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ebupt.migu.music.autotri.entity.message.Body;
import com.ebupt.migu.music.autotri.entity.message.MessageInfo;
import com.ebupt.migu.music.autotri.entity.message.UserSender;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.HtmlUtils;

import com.alibaba.fastjson.JSONObject;
import com.ebupt.migu.music.autotri.entity.ResultMsg;
import com.ebupt.migu.music.autotri.entity.push.ApiResponse;
import com.ebupt.migu.music.autotri.entity.push.PushContentObject;
import com.ebupt.migu.music.autotri.entity.push.PushPolicyObject;
import com.ebupt.migu.music.autotri.entity.push.PushRequestBody;
import com.ebupt.migu.music.autotri.mapper.AutoTriMapper;
import com.ebupt.migu.music.autotri.mapper.EffectTotalMapper;
import com.ebupt.migu.music.autotri.service.AutoTriService;
import com.ebupt.migu.music.autotri.service.MoreTemService;
import com.ebupt.migu.music.commons.constants.Sysconst;
import com.ebupt.migu.music.commons.sms.Sender;
import com.ebupt.migu.music.commons.utils.DateUtil;
import com.ebupt.migu.music.commons.utils.HttpUtils;
import com.ebupt.migu.music.commons.utils.JsonUtils;
import com.ebupt.migu.music.redis.service.RedisService;
import com.ebupt.migu.restapi.IopMatchPost;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class AutoTriServiceImpl implements AutoTriService {

    /**
     * push策略参数
     */
    @Value("${push.ip}")
    private String PUSH_IP;
    @Value("${push.app_id}")
    private String PUSH_APP_ID;
    @Value("${push.app_key}")
    private String PUSH_APP_KEY;
    @Value("${push.app_secret}")
    private String PUSH_APP_SECRET;
    @Value("${push.push_url}")
    private String PUSH_URL;
    @Value("${path.image_server_path}")
    private String imageServerPath;
    @Value("${push.sys_url}")
    private String sysUrl;

    /**
     * 弹窗策略参数
     */
    @Value("${popup.ip}")
    private String POPUP_IP;
    @Value("${popup.port}")
    private int POPUP_PORT;
    @Value("${popup.user_name}")
    private String POPUP_USER_NAME;
    @Value("${popup.password}")
    private String POPUP_PASSWORD;
    @Value("${popup.local_path}")
    private String POPUP_LOCAL_PATH;
    @Value("${popup.remote_path}")
    private String POPUP_REMOTE_PATH;

    /**
     * 消息通知策略参数
     */
    @Value("${msg.appid}")
    private String MSG_APP_ID;
    @Value("${msg.account}")
    private String MSG_ACCOUNT;
    @Value("${msg.password}")
    private String MSG_PASSWORD;
    @Value("${msg.userip}")
    private String MSG_USER_IP;
    @Value("${msg.appkey}")
    private String MSG_APP_KEY;
    @Value("${msg.channelid}")
    private String MSG_CHANNEL_ID;
    @Value("${msg.url}")
    private String MSG_URL;
    @Value("${msg.cdn_server}")
    private String MSG_CDN_SERVER;

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private AutoTriMapper autoTriMapper;
    @Autowired
    private EffectTotalMapper effectTotalMapper;
    @Autowired
    private RedisService redisService;
    @Autowired
    private MoreTemService moreTemService;

    @Override
    public ResultMsg autoTri(Map<String, Object> param) {
        ResultMsg res = new ResultMsg();
        // 策略类型
        String tacMode = param.get("tacMode").toString();
        if (StringUtils.isEmpty(tacMode)) {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("策略类型为空");
            return res;
        }
        // 策略id
        String tacId = param.get("tacID").toString();
        if (StringUtils.isEmpty(tacId)) {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("策略ID为空");
            return res;
        }
        // 获取策略信息
        Map<String, Object> map = autoTriMapper.qryStrategyById(param);
        if (null == map || map.isEmpty()) {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("当前策略不存在");
            log.info("未找到的策略ID:{}", tacId);
            return res;
        }
        // 查看当前策略是否在生效时间内 2021-07-19 新渠道会员
        if (!isTimeToTac(tacId)) {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("当前策略不在有效期内");
            log.info("当前策略不在有效期内:{}", tacId);
            return res;
        }
        // 用户标识类型
        String type = param.get("type").toString();
        if (!StringUtils.isEmpty(type)) {
            // 用户标识
            String userId = param.get("userID").toString();
            if (StringUtils.isEmpty(userId)) {
                res.setCode(Sysconst.FAIL_CODE);
                res.setMsg("用户标识为空");
            }
            // 判断是否为黑名单用户
            if (isBlackUser(tacId, tacMode, userId)) {
                res.setCode(Sysconst.FAIL_CODE);
                res.setMsg("黑名单用户，不做策略推送");
                return res;
            }
        } else {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("用户标识类型为空");
            return res;
        }

        // 验证是否是iop标签活动，并验证当前用户是否需要触发活动
        String iopTagId = autoTriMapper.queryIopTagIdByTacId(tacId);
        if (null != iopTagId && !"".equals(iopTagId)) {
            IopMatchPost ip = new IopMatchPost();
            if (tacMode.equals(Sysconst.SMS) && null != param.get("userID")
                    && !ip.iopMatch(iopTagId, tacId, String.valueOf(param.get("userID")))) {
                res.setCode(Sysconst.FAIL_CODE);
                res.setMsg("当前用户不在iop标签客群中");
                return res;
            }
            if (!tacMode.equals(Sysconst.SMS) && null != param.get("userID")
                    && !ip.iopMatch(iopTagId, tacId, null, String.valueOf(param.get("userID")))) {
                res.setCode(Sysconst.FAIL_CODE);
                res.setMsg("当前用户不在iop标签客群中");
                return res;
            }
        }

        // 添加参数
        map.putAll(param);
        // 触发时间
        String triTime = DateUtil.date2str(new Date(), DateUtil.TRI_TIME);
        log.info("触发时间：{}", triTime);
        log.info("执行策略：{}", tacId);
        // 根据策略类型选择触点发送
        switch (tacMode) {
            case Sysconst.SMS:
                // 短信策略
                res = sendSms(map);
                break;
            case Sysconst.PUSH:
                // push策略
                res = sendPush(map);
                break;
            case Sysconst.POPUP:
                // 弹窗策略
                res = sendPopup(map);
                break;
            case Sysconst.MSG:
                // 消息通知
                res = sendMsg(map);
                break;
            default:
                res.setCode("9999");
                res.setMsg("没有可执行的策略类型");
                break;
        }
        Map<String, String> result = new HashMap<>(5);
        result.put("tacID", param.get("tacID").toString());
        result.put("tacMode", param.get("tacMode").toString());
        result.put("userID", param.get("userID").toString());
        result.put("type", param.get("type").toString());
        result.put("triTime", triTime);
        res.setData(result);
        return res;
    }

    /**
     * 弹窗定时任务
     */
    @Override
    public void autoPopup() {
        // 发送时间
        String now = DateUtil.date2str(new Date(), DateUtil.DATE_FORMAT_YYYYMMDDHH);
        // 获取发送文件夹所有文件
        File[] files = new File(POPUP_LOCAL_PATH).listFiles();
        // 遍历文件夹，发送符合当前时间的文件
        if (files != null) {
            String[] dateStr;
            for (File file : files) {
                if (file.isFile()) {
                    dateStr = file.getName().split("_");
                    if (dateStr.length > 2 && dateStr[1].equals(now)) {
                        // 发送文件
                        transport(file.getName());
                    }
                }
            }
        }
        log.info("弹窗文件推送完成");
    }

    /**
     * 刷入渠道白名单
     */
    @Override
    public void channels() {
        Set channels = new HashSet<>();
        channels = autoTriMapper.channels();
        if (redisTemplate.hasKey("channels")) {
            redisTemplate.delete("channels");
        }
        SetOperations<Serializable, Object> operations = redisTemplate.opsForSet();
        for (Object str : channels) {
            operations.add("channels", str);
            log.info(String.valueOf(str));
        }
    }

    /**
     * 批量处理文件
     */
    private void batchExecute(String popupId, String fileDate, List<String> uids, List<String> imeis,
                              List<String> phones) {
        File f1 = createFile(uids, POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_UID");
        File f2 = createFile(imeis, POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_IMEI");
        File f3 = createFile(phones, POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_MSISDN");
        // 传输文件
        if (f1.length() != 0) {
            transport(popupId + "_" + fileDate + "_UID");
        }
        if (f2.length() != 0) {
            transport(popupId + "_" + fileDate + "_IMEI");
        }
        if (f3.length() != 0) {
            transport(popupId + "_" + fileDate + "_MSISDN");
        }
    }

    /**
     * 短信策略
     *
     * @param param
     * @return
     */
    private ResultMsg sendSms(Map<String, Object> param) {
        ResultMsg res = new ResultMsg();
        // 策略id
        String tacId = param.get("tacId").toString();
        // 手机号码
        String msisdn = param.get("userID").toString();
        // 短信模板id
        String templateId = param.get("templateId").toString();
        log.info("模板ID：{}", templateId);
        // 短信模板内容
        String content = param.get("smsContent").toString();
        // 自动匹配用户 替换文本内容
        content = reContext(content, msisdn, param);
        log.info("短信内容：{}", content);
        String[] params = getParams(content);
        // 发送短信
        String[] mobile = {msisdn};
        log.info("短信发送，手机号{}", msisdn);

        boolean send = Sender.sendSMS(templateId, mobile, params);
        // boolean send = true;
        if (send) {
            param.put("success", 1);
            res.setMsg("短信发送成功");
        } else {
            param.put("fail", 1);
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("短信发送失败");
        }
        // 短信策略效果统计
        param.put("now", DateUtil.date2str(new Date(), DateUtil.DATE_FORMAT_YYYY_MM_DD));
        try {
            // 根据策略id与当天时间判断是否存在
            Map<String, Object> map = effectTotalMapper.qryCen(param);
            if (null == map || map.isEmpty()) {
                // 新建当天的当前策略效果统计表
                effectTotalMapper.insertCenSms(param);
            } else {
                // 更新短信发送效果统计
                effectTotalMapper.updateCenSms(param);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * push策略
     *
     * @param param
     * @return
     */
    private ResultMsg sendPush(Map<String, Object> param) {
        ResultMsg res = new ResultMsg();
        if (null == param.get("pushUrl")){
            param.put("pushUrl", (null == param.get("sourceUrl") ? " " : param.get("sourceUrl")));
        }
        // 多模板随机获取
        param.putAll(moreTemService.randomTem(param));
        // 策略id
        String tacId = param.get("tacId").toString();
        // 任务id
        String taskId = tacId + DateUtil.date2str(new Date(), DateUtil.DATE_FORMAT_YYYYMMDD);
        log.info("任务id:{}", taskId);
        // 构造PushContentObject实例
        PushContentObject pushContentObject = new PushContentObject();
        pushContent(param, pushContentObject);
        // 标题
        String topic = param.get("pushTitle").toString();
        // push策略uuid数组
        List<String> uids = new ArrayList<>();
        // push策略设备数组
        List<String> dids = new ArrayList<>();
        // 用户标识
        String userId = param.get("userID").toString();
        String type = param.get("type").toString();
        if (Sysconst.UUID.equals(type)) {
            uids.add(userId);
        }
        if (Sysconst.IMEI_IDFA.equals(type)) {
            dids.add(userId);
        }
        // 触发场景和链接类型
        Map<String, Object> reMap = autoTriMapper.qryTacInfo(tacId);
        if (reMap.get("triSceneDetail").equals(Sysconst.SIMILAR_RECOMMEND)) {
            // 触发场景为相似推荐且链接为系统默认时,拼接千人千面相似歌曲id
            if (param.get("likeSingId") != null && param.get("pushUrl").equals(sysUrl)) {
                pushContentObject
                        .setPushLinkAddress(pushContentObject.getPushLinkAddress() + param.get("likeSingId").toString());
                log.info("相似推荐url：{}", pushContentObject.getPushLinkAddress());
            } else {
                res.setCode(Sysconst.FAIL_CODE);
                res.setMsg("没有找到相似推荐歌曲ID");
                return res;
            }
        }
        // 关注歌手
        if (reMap.get("triSceneDetail").equals(Sysconst.SINGER_FANS)) {
            log.info("关注歌手url：{}", param.get("sourceUrl"));
            pushContentObject.setPushLinkAddress(String.valueOf(param.get("sourceUrl")));
        }
        // 自动匹配用户 替换文本内容
        topic = reContext(topic, userId, param);
        log.info("标题:{}", topic);
        pushContentObject.setPushContent(reContext(pushContentObject.getPushContent(), userId, param));
        pushContentObject.setPushTitle(reContext(pushContentObject.getPushTitle(), userId, param));
        log.info("替换后的pushContentObject:{}", pushContentObject.toString());
        // push推送
        ApiResponse apiResponse = pushToServer(taskId, pushContentObject, uids, dids, topic);
        if (apiResponse != null) {
            // 增加cid存储 2020/08/21 添加
            param.put("cid", apiResponse.getData().toString());
            if (apiResponse.getCode().equals(Sysconst.SUCCESS)) {
                res.setMsg("push推送成功");
            } else {
                res.setCode(Sysconst.FAIL_CODE);
                res.setMsg("push推送失败");
            }
        } else {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("push推送异常");
        }
        // push策略效果统计
        param.put("now", DateUtil.date2str(new Date(), DateUtil.DATE_FORMAT_YYYY_MM_DD));
        try {
            // 根据策略id与当天时间判断是否存在
            Map<String, Object> map = effectTotalMapper.qryCen(param);
            if (null == map || map.isEmpty()) {
                // 新建当天的当前策略效果统计表
                effectTotalMapper.insertCenPush(param);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 弹窗策略
     *
     * @param param
     * @return
     */
    private ResultMsg sendPopup(Map<String, Object> param) {
        ResultMsg res = new ResultMsg();
        // 弹窗id
        String popupId = param.get("popupId").toString();
        // 存储弹窗信息
        // Map<String,Object> map = new HashMap<>(5);
        // 弹窗userId数组
        String[] popups = param.get("userID").toString().split(",");
        // 生成的文件需要发送时间（小时为单位往后取整）
        String fileDate =
                DateUtil.date2str(new Date(System.currentTimeMillis() + 1000 * 60 * 60), DateUtil.DATE_FORMAT_YYYYMMDDHH);
        boolean flag = false;
        // popup[0] = uuid popup[1] = imei/idfa popup[2] = msisdn
        switch (popups.length) {
            case 1:
                // 只有uuid的情况
                boolean b1 = inputFile(popups[0], POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_UID");
                if (b1) {
                    flag = true;
                }
                break;
            case 2:
                // 有uuid和设备id的情况
                boolean b2 = inputFile(popups[0], POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_UID");
                boolean b3 = inputFile(popups[1], POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_IMEI");
                if (b2 && b3) {
                    flag = true;
                }
                break;
            default:
                // 三种情况都有
                boolean b4 = inputFile(popups[0], POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_UID");
                boolean b5 = inputFile(popups[1], POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_IMEI");
                boolean b6 = inputFile(popups[3], POPUP_LOCAL_PATH + popupId + "_" + fileDate + "_MSISDN");
                if (b4 && b5 && b6) {
                    flag = true;
                }
                break;
        }
        if (flag) {
            res.setMsg("当前弹窗策略信息保存成功");
        } else {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("当前弹窗策略信息保存失败");
        }
        return res;
    }

    /**
     * 弹窗数据写文件
     *
     * @param key
     * @param path
     */
    private boolean inputFile(String key, String path) {
        FileWriter fw = null;
        try {
            fw = new FileWriter(path, true);
            fw.write(key);
            fw.write("\r\n");
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fw) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    /**
     * 消息通知
     *
     * @param param
     * @return
     */
    private ResultMsg sendMsg(Map<String, Object> param) {
        ResultMsg res = new ResultMsg();
        // 用户标识
        String userId = param.get("userID").toString();
        // 任务id
        String taskId = param.get("tacId").toString() + DateUtil.date2str(new Date(), DateUtil.DATE_FORMAT_YYYYMMDD);
        log.info("任务id:{}", taskId);
        // 生成消息通知body
        Body bodys = body(param, userId);
        MessageInfo messageInfo = new MessageInfo();
        messageInfo.setType(0);
        messageInfo.setAction("11");
        messageInfo.setBody(bodys);
        // task id
        messageInfo.setTaskId(taskId);
        JSONObject params = new JSONObject();
        params.put("userID", "-1");
        List<String> targetIDList = new ArrayList<>();
        targetIDList.add(userId);
        params.put("targetIDList", targetIDList);
        params.put("messageInfo", messageInfo);
        JSONObject sendMessageReq = new JSONObject();
        sendMessageReq.put("sendMessageReq", params);
        // 发送消息通知
        String response = doPost(MSG_URL, sendMessageReq.toJSONString());
        log.info("消息通知返回结果：{}", response);
        if (null != response && !"".equals(response)) {
            try {
                JSONObject responseJson = JSONObject.parseObject(response);
                String code = responseJson.getString("code");
                if ("000000".equals(code)) {
                    param.put("successNum", 1);
                    res.setMsg("消息通知推送成功");
                } else {
                    res.setCode(Sysconst.FAIL_CODE);
                    res.setMsg("消息通知推送失败");
                }
            } catch (Exception e) {
                log.error("消息通知请求下发异常：{}", e.getMessage());
                res.setCode(Sysconst.FAIL_CODE);
                res.setMsg("消息通知推送失败");
            }
        } else {
            res.setCode(Sysconst.FAIL_CODE);
            res.setMsg("消息通知推送失败");
        }
        // 消息通知策略效果统计
        param.put("now", DateUtil.date2str(new Date(), DateUtil.DATE_FORMAT_YYYY_MM_DD));
        param.put("taskId", taskId);
        param.put("totalSend", 1);
        try {
            // 根据策略id与当天时间判断是否存在
            Map<String, Object> map = effectTotalMapper.qryCen(param);
            if (null == map || map.isEmpty()) {
                // 新建当天的当前策略效果统计表
                effectTotalMapper.insertCenMsg(param);
            } else {
                effectTotalMapper.updateCenMsg(param);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 提取{}中的参数并以数组形式返回
     *
     * @param str
     * @return
     */
    private String[] getParams(String str) {
        List<String> params = new ArrayList<>();
        // 提取{}中的参数
        Pattern pattern = Pattern.compile("(?<=\\{)[^\\}]+");
        Matcher matcher = pattern.matcher(str);
        while (matcher.find()) {
            params.add(matcher.group());
        }
        // 提取不到参数
        if (null == params || params.size() == 0) {
            return null;
        }
        // 生成String[]
        String[] tplVars = new String[params.size()];
        for (int i = 0; i < params.size(); i++) {
            tplVars[i] = params.get(i);
        }
        log.info("提取到大括号中的参数:{}", tplVars.toString());
        return tplVars;
    }

    /**
     * 判断当前用户标识是否在黑名单中
     */
    private boolean isBlackUser(String tacId, String tacMode, String userId) {
        // 查询黑名单名单数组
        String black = autoTriMapper.queryBlack(tacId);
        if (StringUtils.isEmpty(black)) {
            // 当前策略没有黑名单分组
            return false;
        }
        String[] blacks = black.split(",");
        // 查询黑名单人员信息
        List<Map<String, Object>> list = autoTriMapper.qryBlackUser(blacks);
        for (Map<String, Object> map : list) {
            switch (tacMode) {
                // 策略类型 -- 短信策略
                case Sysconst.SMS:
                    if (userId.equals(map.get("msisdn").toString())) {
                        return true;
                    }
                    break;
                // 策略类型 -- push策略
                case Sysconst.PUSH:
                    if (userId.equals(map.get("uuid").toString()) || userId.equals(map.get("imei").toString())
                            || userId.equals(map.get("idfa").toString())) {
                        return true;
                    }
                    break;
                // 策略类型 -- 弹窗策略
                case Sysconst.POPUP:
                    // 弹窗userId数组 (uuid, imei/idfa, msisdn)
                    String[] popup = userId.split(",");
                    // popup[0] = uuid popup[1] = imei/idfa popup[2] = msisdn
                    if (popup.length == 1) {
                        // 只有uuid
                        if (popup[0].equals(map.get("uuid").toString())) {
                            return true;
                        }
                    } else if (popup.length == 2) {
                        // 有uuid 和 设备id
                        if (popup[0].equals(map.get("uuid").toString()) || popup[1].equals(map.get("imei").toString())
                                || popup[1].equals(map.get("idfa").toString())) {
                            return true;
                        }
                    } else {
                        // 三种情况都有可能
                        if (popup[0].equals(map.get("uuid").toString()) || popup[1].equals(map.get("imei").toString())
                                || popup[1].equals(map.get("idfa").toString())
                                || popup[2].equals(map.get("msisdn").toString())) {
                            return true;
                        }
                    }
                    break;
                // 策略类型 -- 消息通知
                case Sysconst.MSG:
                    if (userId.equals(map.get("uuid").toString())) {
                        return true;
                    }
                    break;
                default:
                    break;
            }
        }
        return false;
    }

    /**
     * 构造PushContentObject实例
     *
     * @param strategy
     * @param pushContentObject
     */
    private void pushContent(Map<String, Object> strategy, PushContentObject pushContentObject) {
        // 需要进行HTML解码
        pushContentObject.setPushTitle(HtmlUtils.htmlUnescape(strategy.get("pushTitle").toString()));
        pushContentObject.setPushContent(HtmlUtils.htmlUnescape(strategy.get("pushContent").toString()));
        // 默认值是1
        pushContentObject.setPushType(1);
        // 1.前端输入链接以：mgmusic开头的，pushLinkType=100
        // 2.输入链接以http或以https开头的，pushLinkType=9
        if (null != strategy.get("pushUrl")) {
            String pushURL = strategy.get("pushUrl").toString();
            if (pushURL.startsWith("mgmusic")) {
                pushContentObject.setPushLinkType(100);
            } else {
                pushContentObject.setPushLinkType(9);
            }
            pushContentObject.setPushLinkAddress(pushURL);
        }
        // 多模板补白空格时，也不添加图片路径
        if (null != strategy.get("picDir") && !"".equals(strategy.get("picDir"))
                && !" ".equals(strategy.get("picDir"))) {
            String picDir = strategy.get("picDir").toString();
            // picDir = imageServerPath + picDir.substring(picDir.lastIndexOf("/") + 1);
            // picDir = picDir.substring(picDir.lastIndexOf("/") + 1);
            pushContentObject.setImgUrl(picDir);
        }
        pushContentObject.setPushCreateTime(System.currentTimeMillis());
        log.info("push content object:{}", pushContentObject.toString());
    }

    /**
     * push推送
     *
     * @param taskId
     * @param pushContentObject
     */
    private ApiResponse pushToServer(String taskId, PushContentObject pushContentObject, List<String> uids,
                                     List<String> dids, String topic) {
        PushRequestBody body = new PushRequestBody();
        // policy固定值
        PushPolicyObject policy = new PushPolicyObject();
        policy.setBroadcast(false);
        policy.setIsOffline(true);
        policy.setPushNetWorkType(0);
        body.setPolicy(policy);
        body.setAppId(PUSH_APP_ID);
        body.setTaskId(taskId);
        body.setContent(pushContentObject);
        body.setUid(uids);
        body.setDid(dids);
        body.setTopic(HtmlUtils.htmlUnescape(topic));
        body.setPushChannel("1");
        body.setIsSync(true);
        // 转Map
        Map<String, Object> params = JsonUtils.beanToMap(body);
        // 生成Header
        Map<String, String> header = header(taskId, body);
        // URL
        String url = PUSH_IP + PUSH_URL;
        log.info("服务端URL: {}", url);
        String response = HttpUtils.doPost(url, params, header);
        ApiResponse apiResponse = JsonUtils.json2Obj(response, ApiResponse.class);
        return apiResponse;
    }

    /**
     * 请求签名 使用场景: 接入方发送HTTP请求时需要对请求做签名校验 签名方式: Map<String, Object> headers = new HashMap<>(); StringBuilder sb = new
     * StringBuilder("MIGU").append("\n"); sb.append(taskId).append("\n"); sb.append(pushChannel).append("\n");
     * sb.append(pushContent).append("\n"); sb.append(timestamp).append("\n"); sb.append(AppSecret).append("\n"); String
     * Authorization = AppKey + ":" + MD5Util.MD5(sb.toString()); headers.put("Authorization", Authorization);
     * headers.put("timestamp", timestamp); 其中: MIGU:固定值 \n:换行转义符 taskId: 任务id,唯一标示 pushChannel: 推送渠道1-个推 当前只支持个推渠道
     * pushContent:传输到推送系统的content.getPushContent() 详见Content Object timestamp:系统时间戳 AppKey :推送系统分配的appKey
     * AppSecret:推送系统分配的密钥 WARN: 接入方系统时间戳跟推送系统需要保持基本一致，防止重放攻击 推送说明: broadcast为true时表示针对app做推送 此时platform生效，表示对手机系统app推送
     * 如果推送列表 broadcast必为false, uid或者cid列表长度大于1。 当前只支持个推推送，所有pushChannel固定为1。 当uid或者cid长度为1时表示对单个用户进行推送。 md5值为小写md5
     */
    private Map<String, String> header(String taskId, PushRequestBody body) {
        Map<String, String> map = new HashMap<>();

        String timestamp = String.valueOf(System.currentTimeMillis());
        StringBuilder sb = new StringBuilder("MIGU").append("\n");
        sb.append(taskId).append("\n");
        sb.append(body.getPushChannel()).append("\n");
        sb.append(body.getContent().getPushContent()).append("\n");
        sb.append(timestamp).append("\n");
        sb.append(PUSH_APP_SECRET).append("\n");
        String Authorization = PUSH_APP_KEY + ":" + DigestUtils.md5Hex(sb.toString());
        map.put("Content-Type", "application/json; charset=utf-8");
        map.put("Authorization", Authorization);
        map.put("timestamp", timestamp);
        return map;
    }

    /**
     * 构造body
     *
     * @param map
     * @return
     */
    private Body body(Map<String, Object> map, String userId) {
        String msgNickName = map.get("msgNickName").toString();
        Map<String, Object> info = autoTriMapper.queryMsgTypeInfo(msgNickName);
        Body body = new Body();
        if (null != map.get("msgText")) {
            body.setText(reContext(map.get("msgText").toString(), userId, map));
        }
        if (null != map.get("msgPic") && !"".equals(map.get("msgPic").toString())) {
            String msg_pic = map.get("msgPic").toString();
            String dir = MSG_CDN_SERVER + msg_pic.substring(msg_pic.lastIndexOf("/") + 1);
            try {
                body.setPic(URLEncoder.encode(dir, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error("pic's url encode error:{}", e.getMessage());
            }
        }
        if (null != map.get("msgLink")) {
            try {
                body.setLink(URLEncoder.encode(map.get("msgLink").toString(), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error("msg_link's url encode error:{}", e.getMessage());
            }
        }
        if (null != map.get("sourceUrl")) {
            try {
                body.setLink(URLEncoder.encode(map.get("sourceUrl").toString(), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error("msg_link's url encode error:{}", e.getMessage());
            }
        }
        UserSender sender = new UserSender();
        String icon = info.get("icon").toString();
        String icon_dir = MSG_CDN_SERVER + icon.substring(icon.lastIndexOf("/") + 1);
        try {
            sender.setIcon(URLEncoder.encode(icon_dir, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            log.error("icon's url encode error:{}", e.getMessage());
        }
        sender.setNickName(HtmlUtils.htmlEscape(msgNickName));
        body.setSender(sender);
        return body;
    }

    /**
     * HTTP请求
     *
     * @param url
     * @param request
     * @return
     */
    private String doPost(String url, String request) {
        log.info("访问参数:{}", request);
        RestTemplate restTemplate = new RestTemplate();
        // 设置编码集
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", authorization());
        HttpEntity<String> entity = new HttpEntity<>(request, headers);
        ResponseEntity response = restTemplate.postForEntity(url, entity, String.class);
        if (null != response) {
            return response.getBody().toString();
        } else {
            return "请求异常！";
        }
    }

    /**
     * header authorization -用户验证信息
     *
     * @return
     */
    private String authorization() {
        // part1： 密码
        String password = MSG_PASSWORD;
        // part2： 6位随机码
        String nonce = getRandom();
        // part3： created时间戳
        String created = DateUtil.date2str(new Date(), DateUtil.TRI_TIME);
        // 加密
        String password_base_64 = base_64_encode(sha_256(password + nonce + created));
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("Basicappid=\"");
        stringBuffer.append(MSG_APP_ID);
        stringBuffer.append("\",account=\"");
        stringBuffer.append(MSG_ACCOUNT);
        stringBuffer.append("\",password=\"");
        stringBuffer.append(password_base_64);
        stringBuffer.append("\",nonce=\"");
        stringBuffer.append(nonce);
        stringBuffer.append("\",created=\"");
        stringBuffer.append(created);
        stringBuffer.append("\",userip=\"");
        stringBuffer.append(MSG_USER_IP);
        stringBuffer.append("\",appkey=\"");
        stringBuffer.append(MSG_APP_KEY);
        stringBuffer.append("\",channelid=\"");
        stringBuffer.append(MSG_CHANNEL_ID);
        stringBuffer.append("\"");
        String auth = stringBuffer.toString();
        log.info("header authorization -用户验证信息:{}", auth);
        return auth;
    }

    /**
     * sha-256加密算法
     *
     * @param str
     * @return
     */
    public static byte[] sha_256(String str) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes());
            return messageDigest.digest();
        } catch (NoSuchAlgorithmException e) {
            log.error("SHA-256加密出错:{}", e.getMessage());
        }
        log.error("sha-256加密失败");
        return null;
    }

    /**
     * 获取6位长度随机数
     *
     * @return
     */
    public static String getRandom() {
        StringBuffer val = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            val.append((random.nextInt(10)));
        }
        return val.toString();
    }

    /**
     * base64进行加密
     *
     * @return
     */
    private static String base_64_encode(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    /**
     * 生成文件
     */
    private File createFile(List<String> keys, String path) {
        File file = new File(path);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
            Iterator<String> it = keys.iterator();
            while (it.hasNext()) {
                bw.write(it.next());
                bw.newLine();
            }
            bw.close();
            log.info("生成文件：{}", path);
        } catch (IOException e) {
            log.info("生成文件报错:{}", path, e);
            e.printStackTrace();
        }
        return file;
    }

    /**
     * 文件传输
     *
     * @param filename
     */
    private void transport(String filename) {
        FTPClient ftpClient = new FTPClient();

        try {
            ftpClient.setUseEPSVwithIPv4(true);
            // 连接服务器
            ftpClient.connect(POPUP_IP, POPUP_PORT);
            log.info("IP:{}, PORT:{}", POPUP_IP, POPUP_PORT);
            // 登录ftp
            ftpClient.login(POPUP_USER_NAME, POPUP_PASSWORD);
            log.info("USERNAME:{}, PASSWORD:{}", POPUP_USER_NAME, POPUP_PASSWORD);
            // 设置编码格式
            ftpClient.setControlEncoding("UTF-8");
            // 启动被动模式
            ftpClient.enterLocalPassiveMode();
            // 需要上传的文件
            InputStream inputStream = new FileInputStream(new File(POPUP_LOCAL_PATH + filename));
            // 指定写入的目录,远程路径不存在则创建
            boolean isChanged = ftpClient.changeWorkingDirectory(POPUP_REMOTE_PATH);
            log.info("切换到指定路径:{}，切换结果:{}", POPUP_REMOTE_PATH, isChanged);
            if (isChanged) {
                boolean result = ftpClient.storeFile(new String(filename.getBytes("UTF-8"), "UTF-8"), inputStream);
                log.info("传送文件结果:{}, 文件名:{}", result, filename);
            }
            inputStream.close();
            ftpClient.logout();
        } catch (Exception e) {
            log.error("FTP异常", e);
        } finally {
            if (ftpClient.isConnected()) {
                try {
                    log.info("关闭FTP连接");
                    ftpClient.disconnect();
                } catch (IOException e) {
                    log.error("FTP连接关闭异常", e);
                }
            }
        }
    }

    /**
     * 文本内容替换 替换各个触点的文本内容，达到千人千面 原内容：亲爱的$userName:白金会员$，您的$sing:歌曲$即将到期！ 从redis中读取关键的key 例如$userName $sing 能取到值为redis中的值
     * 如果redis中取不到值，则用文本中默认的内容 例如 userName--白金会员 sing--歌曲 替换后的（redis有值）： 亲爱的 redis值，您的 redis值 即将到期！ 替换后的（redis没有值）： 亲爱的
     * 白金会员，您的 歌曲 即将到期！
     */
    private String reContext(String context, String uid, Map<String, Object> map) {
        Pattern pattern = Pattern.compile("(\\$[a-zA-Z]+)");
        Matcher matcher = pattern.matcher(context);
        if (matcher.find()) {
            // 获取匹配key userName
            String key = matcher.group().substring(1);
            // 获取要替换的文本内容 $userName:白金会员$
            String str =
                    context.substring(context.indexOf("$", matcher.start()), context.indexOf("$", matcher.end()) + 1);
            // 获取默认的值 白金会员
            String defaultVal = str.substring(str.indexOf(":") + 1, str.length() - 1);
            // 拼接redis key
            if ("userName".equals(key)) {
                // 默认用手机号或者uuid直接取用户名
                key = uid;
                // 从redis 用户信息 _user_info 中取数据
                context = redisService.getHashVal(key, "_user_info") != null
                        ? context.replace(str, redisService.getHashVal(key, "_user_info").toString())
                        : context.replace(str, defaultVal);
            } else {
                boolean flag = false;
                // 因redis存储问题，用户名以外的替换通过传参来处理
                for (Map.Entry<String, Object> entry : map.entrySet()) {
                    if (entry.getKey().equals(key)) {
                        flag = true;
                        // 找到匹配的key,替换值
                        context = entry.getValue() != null ? context.replace(str, entry.getValue().toString())
                                : context.replace(str, defaultVal);
                        break;
                    }
                }
                if (!flag) {
                    context = context.replace(str, defaultVal);
                }
            }
            // 递归再次调用该方法，直到需要替换的内容全部替换
            return reContext(context, uid, map);
        } else {
            return context;
        }
    }

    /**
     * 获取当前策略的生效周期
     *
     * @param tacID
     * @return
     */
    public boolean isTimeToTac(String tacID) {

        /**
         * 仅针对渠道新用户策略 非渠道新用户策略，默认在有效期内
         */
        if (!autoTriMapper.isChannelTac(tacID)) {
            return true;
        }
        String redisKey = "new_channel_tacs";
        HashOperations operations = redisTemplate.opsForHash();
        Map<String, String> map = autoTriMapper.isTimeToTac(tacID);
        if (null == map || null == map.get("triPeriod")) {
            log.info("渠道策略{}已过期，删除redis信息", tacID);
            operations.delete(redisKey, tacID);
            return false;
        }
        String period = map.get("triPeriod");
        if ("1".equals(period)) {
            SimpleDateFormat formatter = new SimpleDateFormat("dd");
            Date date = new Date(System.currentTimeMillis());
            int today = Integer.valueOf(formatter.format(date));
            int start = Integer.valueOf(map.get("startDay"));
            int end = Integer.valueOf(map.get("endDay"));
            return today > start ? end > today : false;
        }
        return true;
    }
}
